/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Shader API tests.
 *//*--------------------------------------------------------------------*/

#include "es3fShaderApiTests.hpp"
#include "es3fApiCase.hpp"
#include "tcuTestLog.hpp"

#include "gluRenderContext.hpp"
#include "gluShaderProgram.hpp"
#include "gluShaderUtil.hpp"
#include "gluDrawUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluCallLogWrapper.hpp"

#include "glwFunctions.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include "deString.h"

#include "deRandom.hpp"
#include "deStringUtil.hpp"

#include <string>
#include <sstream>
#include <vector>
#include <map>

using namespace glw; // GL types

namespace deqp
{
namespace gles3
{
namespace Functional
{

using tcu::TestLog;

namespace
{

enum ShaderSourceCaseFlags
{
	CASE_EXPLICIT_SOURCE_LENGTHS	= 1,
	CASE_RANDOM_NULL_TERMINATED		= 2
};

struct ShaderSources
{
	std::vector<std::string>	strings;
	std::vector<int>			lengths;
};

// Simple shaders

const char* getSimpleShaderSource (const glu::ShaderType shaderType)
{
	const char* simpleVertexShaderSource =
		"#version 300 es\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = vec4(0.0);\n"
		"}\n";

	const char* simpleFragmentShaderSource =
		"#version 300 es\n"
		"layout(location = 0) out mediump vec4 o_fragColor;\n"
		"void main (void)\n"
		"{\n"
		"	o_fragColor = vec4(0.0);\n"
		"}\n";

	switch (shaderType)
	{
		case glu::SHADERTYPE_VERTEX:
			return simpleVertexShaderSource;
		case glu::SHADERTYPE_FRAGMENT:
			return simpleFragmentShaderSource;
		default:
			DE_ASSERT(DE_FALSE);
	}

	return 0;
}

void setShaderSources (glu::Shader& shader, const ShaderSources& sources)
{
	std::vector<const char*> cStrings (sources.strings.size(), 0);

	for (size_t ndx = 0; ndx < sources.strings.size(); ndx++)
		cStrings[ndx] = sources.strings[ndx].c_str();

	if (sources.lengths.size() > 0)
		shader.setSources((int)cStrings.size(), &cStrings[0], &sources.lengths[0]);
	else
		shader.setSources((int)cStrings.size(), &cStrings[0], 0);
}

void sliceSourceString (const std::string& in, ShaderSources& out, const int numSlices, const size_t paddingLength = 0)
{
	DE_ASSERT(numSlices > 0);

	const size_t		sliceSize			= in.length() / numSlices;
	const size_t		sliceSizeRemainder	= in.length() - (sliceSize * numSlices);
	const std::string	padding				(paddingLength, 'E');

	for (int ndx = 0; ndx < numSlices; ndx++)
	{
		out.strings.push_back(in.substr(ndx * sliceSize, sliceSize) + padding);

		if (paddingLength > 0)
			out.lengths.push_back((int)sliceSize);
	}

	if (sliceSizeRemainder > 0)
	{
		const std::string	lastString			= in.substr(numSlices * sliceSize);
		const int			lastStringLength	= (int)lastString.length();

		out.strings.push_back(lastString + padding);

		if (paddingLength > 0)
			out.lengths.push_back(lastStringLength);
	}
}

void queryShaderInfo (glu::RenderContext& renderCtx, deUint32 shader, glu::ShaderInfo& info)
{
	const glw::Functions& gl = renderCtx.getFunctions();

	info.compileOk		= false;
	info.compileTimeUs	= 0;
	info.infoLog.clear();

	// Query source, status & log.
	{
		int	compileStatus	= 0;
		int sourceLen		= 0;
		int	infoLogLen		= 0;
		int	unusedLen;

		gl.getShaderiv(shader, GL_COMPILE_STATUS,			&compileStatus);
		gl.getShaderiv(shader, GL_SHADER_SOURCE_LENGTH,	&sourceLen);
		gl.getShaderiv(shader, GL_INFO_LOG_LENGTH,		&infoLogLen);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetShaderiv()");

		info.compileOk = compileStatus != GL_FALSE;

		if (sourceLen > 0)
		{
			std::vector<char> source(sourceLen);
			gl.getShaderSource(shader, (int)source.size(), &unusedLen, &source[0]);
			info.source = std::string(&source[0], sourceLen);
		}

		if (infoLogLen > 0)
		{
			std::vector<char> infoLog(infoLogLen);
			gl.getShaderInfoLog(shader, (int)infoLog.size(), &unusedLen, &infoLog[0]);
			info.infoLog = std::string(&infoLog[0], infoLogLen);
		}
	}
}

// Draw test quad

void drawWithProgram (glu::RenderContext& renderCtx, deUint32 program)
{
	const glw::Functions& gl = renderCtx.getFunctions();

	const float position[] =
	{
		-1.0f, -1.0f,  0.0f, 1.0f,
		-1.0f, +1.0f,  0.0f, 1.0f,
		+1.0f, -1.0f,  0.0f, 1.0f,
		+1.0f, +1.0f,  0.0f, 1.0f
	};
	const deUint16 quadIndices[] = { 0, 1, 2, 2, 1, 3 };

	gl.useProgram(program);

	{
		glu::VertexArrayBinding vertexArrays[] =
		{
			glu::va::Float("a_position",	4, 4, 0, &position[0])
		};
		glu::draw(renderCtx, program, DE_LENGTH_OF_ARRAY(vertexArrays), &vertexArrays[0], glu::pr::Triangles(DE_LENGTH_OF_ARRAY(quadIndices), &quadIndices[0]));
	}

	GLU_EXPECT_NO_ERROR(gl.getError(), "Draw test quad");
}

// Shader source generator

class SourceGenerator
{
public:
	virtual				~SourceGenerator	(void)	{}

	virtual std::string	next				(const glu::ShaderType shaderType)			= 0;
	virtual bool		finished			(const glu::ShaderType shaderType) const	= 0;
};

class ConstantShaderGenerator : public SourceGenerator
{
public:
				ConstantShaderGenerator		(de::Random& rnd)	: m_rnd(rnd)	{}
				~ConstantShaderGenerator	(void)								{}

	bool		finished					(const glu::ShaderType shaderType) const	{ DE_UNREF(shaderType); return false; }

	std::string	next						(const glu::ShaderType shaderType);

private:
	de::Random	m_rnd;
};

std::string ConstantShaderGenerator::next (const glu::ShaderType shaderType)
{
	DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);

	const float			value		= m_rnd.getFloat(0.0f, 1.0f);
	const std::string	valueString	= de::toString(value);
	const std::string	outputName	= (shaderType == glu::SHADERTYPE_VERTEX) ? "gl_Position" : "o_fragColor";

	std::ostringstream	out;

	out << "#version 300 es\n";

	if (shaderType == glu::SHADERTYPE_FRAGMENT)
		out << "layout(location = 0) out mediump vec4 o_fragColor;\n";

	out << "void main (void)\n";
	out << "{\n";
	out << "	" << outputName << " = vec4(" << valueString << ");\n";
	out << "}\n";

	return out.str();
}

// Shader allocation utility

class ShaderAllocator
{
public:
					ShaderAllocator		(glu::RenderContext& context, SourceGenerator& generator);
					~ShaderAllocator	(void);

	bool			hasShader			(const glu::ShaderType shaderType);

	void			setSource			(const glu::ShaderType shaderType);

	glu::Shader&	createShader		(const glu::ShaderType shaderType);
	void			deleteShader		(const glu::ShaderType shaderType);

	glu::Shader&	get					(const glu::ShaderType shaderType)	{ DE_ASSERT(hasShader(shaderType)); return *m_shaders[shaderType]; }

private:
	const glu::RenderContext&				m_context;
	SourceGenerator&						m_srcGen;
	std::map<glu::ShaderType, glu::Shader*>	m_shaders;
};

ShaderAllocator::ShaderAllocator (glu::RenderContext& context, SourceGenerator& generator)
	: m_context	(context)
	, m_srcGen	(generator)
{
}

ShaderAllocator::~ShaderAllocator (void)
{
	for (std::map<glu::ShaderType, glu::Shader*>::iterator shaderIter = m_shaders.begin(); shaderIter != m_shaders.end(); shaderIter++)
		delete shaderIter->second;
	m_shaders.clear();
}

bool ShaderAllocator::hasShader (const glu::ShaderType shaderType)
{
	if (m_shaders.find(shaderType) != m_shaders.end())
		return true;
	else
		return false;
}

glu::Shader& ShaderAllocator::createShader (const glu::ShaderType shaderType)
{
	DE_ASSERT(!this->hasShader(shaderType));

	glu::Shader* const	shader	= new glu::Shader(m_context, shaderType);

	m_shaders[shaderType] = shader;
	this->setSource(shaderType);

	return *shader;
}

void ShaderAllocator::deleteShader (const glu::ShaderType shaderType)
{
	DE_ASSERT(this->hasShader(shaderType));

	delete m_shaders[shaderType];
	m_shaders.erase(shaderType);
}

void ShaderAllocator::setSource (const glu::ShaderType shaderType)
{
	DE_ASSERT(this->hasShader(shaderType));
	DE_ASSERT(!m_srcGen.finished(shaderType));

	const std::string	source	= m_srcGen.next(shaderType);
	const char* const	cSource	= source.c_str();

	m_shaders[shaderType]->setSources(1, &cSource, 0);
}

// Logging utilities

void logShader (TestLog& log, glu::RenderContext& renderCtx, glu::Shader& shader)
{
	glu::ShaderInfo info;

	queryShaderInfo(renderCtx, shader.getShader(), info);

	log << TestLog::Shader(getLogShaderType(shader.getType()), info.source, info.compileOk, info.infoLog);
}

void logProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, ShaderAllocator& shaders)
{
	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());

	for (int shaderTypeInt = 0; shaderTypeInt < glu::SHADERTYPE_LAST; shaderTypeInt++)
	{
		const glu::ShaderType shaderType = (glu::ShaderType)shaderTypeInt;

		if (shaders.hasShader(shaderType))
			logShader(log, renderCtx, shaders.get(shaderType));
	}

	log << TestLog::EndShaderProgram;
}

void logVertexFragmentProgram (TestLog& log, glu::RenderContext& renderCtx, glu::Program& program, glu::Shader& vertShader, glu::Shader& fragShader)
{
	DE_ASSERT(vertShader.getType() == glu::SHADERTYPE_VERTEX && fragShader.getType() == glu::SHADERTYPE_FRAGMENT);

	log << TestLog::ShaderProgram(program.getLinkStatus(), program.getInfoLog());

	logShader(log, renderCtx, vertShader);
	logShader(log, renderCtx, fragShader);

	log << TestLog::EndShaderProgram;
}

} // anonymous

// Simple glCreateShader() case

class CreateShaderCase : public ApiCase
{
public:
	CreateShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ApiCase		(context, name, desc)
		, m_shaderType	(shaderType)
	{
	}

	void test (void)
	{
		const GLuint shaderObject = glCreateShader(glu::getGLShaderType(m_shaderType));

		TCU_CHECK(shaderObject != 0);

		glDeleteShader(shaderObject);
	}

private:
	const glu::ShaderType m_shaderType;
};

// Simple glCompileShader() case

class CompileShaderCase : public ApiCase
{
public:
	CompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ApiCase		(context, name, desc)
		, m_shaderType	(shaderType)
	{
	}

	bool checkCompileStatus (const GLuint shaderObject)
	{
		GLint compileStatus = -1;
		glGetShaderiv(shaderObject, GL_COMPILE_STATUS, &compileStatus);
		GLU_CHECK();

		return (compileStatus == GL_TRUE);
	}

	void test (void)
	{
		const char*		shaderSource	= getSimpleShaderSource(m_shaderType);
		const GLuint	shaderObject	= glCreateShader(glu::getGLShaderType(m_shaderType));

		TCU_CHECK(shaderObject != 0);

		glShaderSource(shaderObject, 1, &shaderSource, 0);
		glCompileShader(shaderObject);

		TCU_CHECK(checkCompileStatus(shaderObject));

		glDeleteShader(shaderObject);
	}

private:
	const glu::ShaderType m_shaderType;
};

// Base class for simple program API tests

class SimpleProgramCase : public ApiCase
{
public:
	SimpleProgramCase (Context& context, const char* name, const char* desc)
		: ApiCase		(context, name, desc)
		, m_vertShader	(0)
		, m_fragShader	(0)
		, m_program		(0)
	{
	}

	virtual ~SimpleProgramCase (void)
	{
	}

	virtual void compileShaders (void)
	{
		const char*		vertSource	= getSimpleShaderSource(glu::SHADERTYPE_VERTEX);
		const char*		fragSource	= getSimpleShaderSource(glu::SHADERTYPE_FRAGMENT);

		const GLuint	vertShader	= glCreateShader(GL_VERTEX_SHADER);
		const GLuint	fragShader	= glCreateShader(GL_FRAGMENT_SHADER);

		TCU_CHECK(vertShader != 0);
		TCU_CHECK(fragShader != 0);

		glShaderSource(vertShader, 1, &vertSource, 0);
		glCompileShader(vertShader);

		glShaderSource(fragShader, 1, &fragSource, 0);
		glCompileShader(fragShader);

		GLU_CHECK();

		m_vertShader = vertShader;
		m_fragShader = fragShader;
	}

	void linkProgram (void)
	{
		const GLuint program = glCreateProgram();

		TCU_CHECK(program != 0);

		glAttachShader(program, m_vertShader);
		glAttachShader(program, m_fragShader);
		GLU_CHECK();

		glLinkProgram(program);

		m_program = program;
	}

	void cleanup (void)
	{
		glDeleteShader(m_vertShader);
		glDeleteShader(m_fragShader);
		glDeleteProgram(m_program);
	}

protected:
	GLuint	m_vertShader;
	GLuint	m_fragShader;
	GLuint	m_program;
};

// glDeleteShader() case

class DeleteShaderCase : public SimpleProgramCase
{
public:
	DeleteShaderCase (Context& context, const char* name, const char* desc)
		: SimpleProgramCase (context, name, desc)
	{
	}

	bool checkDeleteStatus(GLuint shader)
	{
		GLint deleteStatus = -1;
		glGetShaderiv(shader, GL_DELETE_STATUS, &deleteStatus);
		GLU_CHECK();

		return (deleteStatus == GL_TRUE);
	}

	void deleteShaders (void)
	{
		glDeleteShader(m_vertShader);
		glDeleteShader(m_fragShader);
		GLU_CHECK();
	}

	void test (void)
	{
		compileShaders();
		linkProgram();
		GLU_CHECK();

		deleteShaders();

		TCU_CHECK(checkDeleteStatus(m_vertShader) && checkDeleteStatus(m_fragShader));

		glDeleteProgram(m_program);

		TCU_CHECK(!(glIsShader(m_vertShader) || glIsShader(m_fragShader)));
	}
};

// Simple glLinkProgram() case

class LinkVertexFragmentCase : public SimpleProgramCase
{
public:
	LinkVertexFragmentCase (Context& context, const char* name, const char* desc)
		: SimpleProgramCase (context, name, desc)
	{
	}

	bool checkLinkStatus (const GLuint programObject)
	{
		GLint linkStatus = -1;
		glGetProgramiv(programObject, GL_LINK_STATUS, &linkStatus);
		GLU_CHECK();

		return (linkStatus == GL_TRUE);
	}

	void test (void)
	{
		compileShaders();
		linkProgram();

		GLU_CHECK_MSG("Linking failed.");
		TCU_CHECK_MSG(checkLinkStatus(m_program), "Fail, expected LINK_STATUS to be TRUE.");

		cleanup();
	}
};

class ShaderSourceReplaceCase : public ApiCase
{
public:
	ShaderSourceReplaceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ApiCase		(context, name, desc)
		, m_shaderType	(shaderType)
	{
	}

	std::string generateFirstSource (void)
	{
		return getSimpleShaderSource(m_shaderType);
	}

	std::string generateSecondSource (void)
	{
		std::ostringstream out;

		out << "#version 300 es\n";
		out << "precision mediump float;\n";

		if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
			out << "layout(location = 0) out mediump vec4 o_fragColor;\n";

		out << "void main()\n";
		out << "{\n";
		out << "	float variable = 1.0f;\n";

		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	out << "	gl_Position = vec4(variable);\n";
		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	out << "	o_fragColor = vec4(variable);\n";

		out << "}\n";

		return out.str();
	}

	GLint getSourceLength (glu::Shader& shader)
	{
		GLint sourceLength = 0;
		glGetShaderiv(shader.getShader(), GL_SHADER_SOURCE_LENGTH, &sourceLength);
		GLU_CHECK();

		return sourceLength;
	}

	std::string readSource (glu::Shader& shader)
	{
		const GLint			sourceLength	= getSourceLength(shader);
		std::vector<char>	sourceBuffer	(sourceLength + 1);

		glGetShaderSource(shader.getShader(), (GLsizei)sourceBuffer.size(), 0, &sourceBuffer[0]);

		return std::string(&sourceBuffer[0]);
	}

	void verifyShaderSourceReplaced (glu::Shader& shader, const std::string& firstSource, const std::string& secondSource)
	{
		TestLog&			log		= m_testCtx.getLog();
		const std::string	result	= readSource(shader);

		if (result == firstSource)
		{
			log << TestLog::Message << "Fail, source was not replaced." << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Shader source nor replaced");
		}
		else if (result != secondSource)
		{
			log << TestLog::Message << "Fail, invalid shader source." << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid source");
		}
	}

	void test (void)
	{
		TestLog&			log				= m_testCtx.getLog();

		glu::Shader			shader			(m_context.getRenderContext(), m_shaderType);

		const std::string	firstSourceStr	= generateFirstSource();
		const std::string	secondSourceStr	= generateSecondSource();

		const char*			firstSource		= firstSourceStr.c_str();
		const char*			secondSource	= secondSourceStr.c_str();

		log << TestLog::Message << "Setting shader source." << TestLog::EndMessage;

		shader.setSources(1, &firstSource, 0);
		GLU_CHECK();

		log << TestLog::Message << "Replacing shader source." << TestLog::EndMessage;

		shader.setSources(1, &secondSource, 0);
		GLU_CHECK();

		verifyShaderSourceReplaced(shader, firstSourceStr, secondSourceStr);
	}

private:
	glu::ShaderType	m_shaderType;
};

// glShaderSource() split source case

class ShaderSourceSplitCase : public ApiCase
{
public:
	ShaderSourceSplitCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType, const int numSlices, const deUint32 flags = 0)
		: ApiCase			(context, name, desc)
		, m_rnd				(deStringHash(getName()) ^ 0x4fb2337d)
		, m_shaderType		(shaderType)
		, m_numSlices		(numSlices)
		, m_explicitLengths	((flags & CASE_EXPLICIT_SOURCE_LENGTHS)	!= 0)
		, m_randomNullTerm	((flags & CASE_RANDOM_NULL_TERMINATED)	!= 0)
	{
		DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
	}

	virtual ~ShaderSourceSplitCase (void)
	{
	}

	std::string generateFullSource (void)
	{
		std::ostringstream out;

		out << "#version 300 es\n";
		out << "precision mediump float;\n";

		if (m_shaderType == glu::SHADERTYPE_FRAGMENT)
			out << "layout(location = 0) out mediump vec4 o_fragColor;\n";

		out << "void main()\n";
		out << "{\n";
		out << "	float variable = 1.0f;\n";

		if		(m_shaderType == glu::SHADERTYPE_VERTEX)	out << "	gl_Position = vec4(variable);\n";
		else if	(m_shaderType == glu::SHADERTYPE_FRAGMENT)	out << "	o_fragColor = vec4(variable);\n";

		out << "}\n";

		return out.str();
	}

	void insertRandomNullTermStrings (ShaderSources& sources)
	{
		const int			numInserts	= de::max(m_numSlices >> 2, 1);
		std::vector<int>	indices		(sources.strings.size(), 0);

		DE_ASSERT(sources.lengths.size() > 0);
		DE_ASSERT(sources.lengths.size() == sources.strings.size());

		for (int i = 0; i < (int)sources.strings.size(); i++)
			indices[i] = i;

		m_rnd.shuffle(indices.begin(), indices.end());

		for (int i = 0; i < numInserts; i++)
		{
			const int			ndx				= indices[i];
			const int			unpaddedLength	= sources.lengths[ndx];
			const std::string	unpaddedString	= sources.strings[ndx].substr(0, unpaddedLength);

			sources.strings[ndx] = unpaddedString;
			sources.lengths[ndx] = m_rnd.getInt(-10, -1);
		}
	}

	void generateSources (ShaderSources& sources)
	{
		const size_t	paddingLength	= (m_explicitLengths ? 10 : 0);
		std::string		str				= generateFullSource();

		sliceSourceString(str, sources, m_numSlices, paddingLength);

		if (m_randomNullTerm)
			insertRandomNullTermStrings(sources);
	}

	void buildProgram (glu::Shader& shader)
	{
		TestLog&				log					= m_testCtx.getLog();
		glu::RenderContext&		renderCtx			= m_context.getRenderContext();

		const glu::ShaderType	supportShaderType	= (m_shaderType == glu::SHADERTYPE_FRAGMENT ? glu::SHADERTYPE_VERTEX : glu::SHADERTYPE_FRAGMENT);
		const char*				supportShaderSource	= getSimpleShaderSource(supportShaderType);
		glu::Shader				supportShader		(renderCtx, supportShaderType);

		glu::Program			program				(renderCtx);

		supportShader.setSources(1, &supportShaderSource, 0);
		supportShader.compile();

		program.attachShader(shader.getShader());
		program.attachShader(supportShader.getShader());

		program.link();

		if (m_shaderType == glu::SHADERTYPE_VERTEX)
			logVertexFragmentProgram(log, renderCtx, program, shader, supportShader);
		else
			logVertexFragmentProgram(log, renderCtx, program, supportShader, shader);
	}

	void test (void)
	{
		TestLog&			log			= m_testCtx.getLog();
		glu::RenderContext&	renderCtx	= m_context.getRenderContext();

		ShaderSources		sources;
		glu::Shader			shader		(renderCtx, m_shaderType);

		generateSources(sources);
		setShaderSources(shader, sources);
		shader.compile();

		buildProgram(shader);

		if (!shader.getCompileStatus())
		{
			log << TestLog::Message << "Compilation failed." << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Compile failed");
		}
	}

private:
	de::Random				m_rnd;

	const glu::ShaderType	m_shaderType;
	const int				m_numSlices;

	const bool				m_explicitLengths;
	const bool				m_randomNullTerm;
};

// Base class for program state persistence cases

class ProgramStateCase : public ApiCase
{
public:
					ProgramStateCase	(Context& context, const char* name, const char* desc, glu::ShaderType shaderType);
	virtual			~ProgramStateCase	(void)	{}

	void			buildProgram		(glu::Program& program, ShaderAllocator& shaders);
	void			verify				(glu::Program& program, const glu::ProgramInfo& reference);

	void			test				(void);

	virtual void	executeForProgram	(glu::Program& program, ShaderAllocator& shaders)	= 0;

protected:
	de::Random					m_rnd;
	const glu::ShaderType		m_shaderType;
};

ProgramStateCase::ProgramStateCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
	: ApiCase		(context, name, desc)
	, m_rnd			(deStringHash(name) ^ 0x713de0ca)
	, m_shaderType	(shaderType)
{
	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
}

void ProgramStateCase::buildProgram (glu::Program& program, ShaderAllocator& shaders)
{
	TestLog&		log			= m_testCtx.getLog();

	glu::Shader&	vertShader	= shaders.createShader(glu::SHADERTYPE_VERTEX);
	glu::Shader&	fragShader	= shaders.createShader(glu::SHADERTYPE_FRAGMENT);

	vertShader.compile();
	fragShader.compile();

	program.attachShader(vertShader.getShader());
	program.attachShader(fragShader.getShader());
	program.link();

	logProgram(log, m_context.getRenderContext(), program, shaders);
}

void ProgramStateCase::verify (glu::Program& program, const glu::ProgramInfo& reference)
{
	TestLog&				log			= m_testCtx.getLog();
	const glu::ProgramInfo&	programInfo	= program.getInfo();

	if (!programInfo.linkOk)
	{
		log << TestLog::Message << "Fail, link status may only change as a result of linking or loading a program binary." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link status changed");
	}

	if (programInfo.linkTimeUs != reference.linkTimeUs)
	{
		log << TestLog::Message << "Fail, reported link time changed." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Link time changed");
	}

	if (programInfo.infoLog != reference.infoLog)
	{
		log << TestLog::Message << "Fail, program infolog changed." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Infolog changed");
	}
}

void ProgramStateCase::test (void)
{
	TestLog&				log			= m_testCtx.getLog();
	glu::RenderContext&		renderCtx	= m_context.getRenderContext();

	ConstantShaderGenerator	sourceGen	(m_rnd);

	ShaderAllocator			shaders		(renderCtx, sourceGen);
	glu::Program			program		(renderCtx);

	buildProgram(program, shaders);

	if (program.getLinkStatus())
	{
		glu::ProgramInfo programInfo = program.getInfo();

		executeForProgram(program, shaders);

		verify(program, programInfo);

		logProgram(log, renderCtx, program, shaders);
	}
	else
	{
		log << TestLog::Message << "Fail, couldn't link program." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
	}
}

// Program state case utilities

namespace
{

template<class T>
void addProgramStateCase (TestCaseGroup* group, Context& context, const std::string& name, const std::string& desc)
{
	for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
	{
		const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
		const std::string		shaderTypeName	= getShaderTypeName(shaderType);

		const std::string		caseName		= name + "_" + shaderTypeName;
		const std::string		caseDesc		= "Build program, " + desc + ", for " + shaderTypeName + " shader.";

		group->addChild(new T(context, caseName.c_str(), caseDesc.c_str(), shaderType));
	}
}

} // anonymous

// Specialized program state cases

class ProgramStateDetachShaderCase : public ProgramStateCase
{
public:
	ProgramStateDetachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateDetachShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Detaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
	}
};

class ProgramStateReattachShaderCase : public ProgramStateCase
{
public:
	ProgramStateReattachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateReattachShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Reattaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		program.attachShader(caseShader.getShader());
	}
};

class ProgramStateDeleteShaderCase : public ProgramStateCase
{
public:
	ProgramStateDeleteShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateDeleteShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Deleting " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		shaders.deleteShader(m_shaderType);
	}
};

class ProgramStateReplaceShaderCase : public ProgramStateCase
{
public:
	ProgramStateReplaceShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateReplaceShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Deleting and replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		shaders.deleteShader(m_shaderType);
		program.attachShader(shaders.createShader(m_shaderType).getShader());
	}
};

class ProgramStateRecompileShaderCase : public ProgramStateCase
{
public:
	ProgramStateRecompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateRecompileShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Recompiling " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		caseShader.compile();
		DE_UNREF(program);
	}
};

class ProgramStateReplaceSourceCase : public ProgramStateCase
{
public:
	ProgramStateReplaceSourceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramStateCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramStateReplaceSourceCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader source and recompiling" << TestLog::EndMessage;
		shaders.setSource(m_shaderType);
		caseShader.compile();
		DE_UNREF(program);
	}
};

// Program binary utilities

namespace
{

struct ProgramBinary
{
	GLenum					format;
	std::vector<deUint8>	data;
};

bool programBinariesEqual (const ProgramBinary& first, const ProgramBinary& second)
{
	if ((first.format != second.format) || (first.data.size() != second.data.size()))
		return false;

	return std::equal(first.data.begin(), first.data.end(), second.data.begin());
}

} // anonymous

// Base class for program binary cases

class ProgramBinaryCase : public TestCase, protected glu::CallLogWrapper
{
public:
							ProgramBinaryCase	(Context& context, const char* name, const char* desc);
	virtual					~ProgramBinaryCase	(void);

	void					getBinaryFormats	(std::vector<GLenum>& out);
	bool					isFormatSupported	(const glw::GLenum format) const;

	void					getProgramBinary	(ProgramBinary& out, GLuint program);
	void					loadProgramBinary	(ProgramBinary& binary, GLuint program);

	void					verifyProgramBinary	(ProgramBinary& binary);

	void					init				(void);
	IterateResult			iterate				(void);

	virtual void			test				(void) = 0;

protected:
	std::vector<GLenum>		m_formats;
};

ProgramBinaryCase::ProgramBinaryCase (Context& context, const char* name, const char* desc)
		: TestCase			(context, name, desc)
		, CallLogWrapper	(context.getRenderContext().getFunctions(), context.getTestContext().getLog())
{
}

ProgramBinaryCase::~ProgramBinaryCase (void)
{
}

void ProgramBinaryCase::getBinaryFormats (std::vector<GLenum>& out)
{
	GLint numFormats = -1;
	glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);

	out.clear();

	if (numFormats > 0)
	{
		out.resize(numFormats, 0);

		glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, (GLint*)&out[0]);
	}
}

bool ProgramBinaryCase::isFormatSupported (const glw::GLenum format) const
{
	return (std::find(m_formats.begin(), m_formats.end(), format) != m_formats.end());
}

void ProgramBinaryCase::getProgramBinary (ProgramBinary& out, GLuint program)
{
	GLint binaryLength = -1;
	glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &binaryLength);

	if (binaryLength > 0)
	{
		GLsizei	actualLength;
		GLenum	format;

		out.data.clear();
		out.data.resize(binaryLength, 0);

		GLU_CHECK_CALL(glGetProgramBinary(program, (GLsizei)out.data.size(), &actualLength, &format, &(out.data[0])));

		TCU_CHECK(actualLength == binaryLength);

		out.format = format;
	}
}

void ProgramBinaryCase::loadProgramBinary (ProgramBinary& binary, GLuint program)
{
	glProgramBinary(program, binary.format, &binary.data[0], (GLsizei)binary.data.size());
	GLU_CHECK_MSG("Failed to load program binary.");
}

void ProgramBinaryCase::verifyProgramBinary (ProgramBinary& binary)
{
	TestLog& log = m_testCtx.getLog();

	if (!isFormatSupported(binary.format))
	{
		log << TestLog::Message << "Program binary format " << binary.format << " is not among the supported formats reported by the platform." << TestLog::EndMessage;

		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid format");
	}
}

void ProgramBinaryCase::init (void)
{
	getBinaryFormats(m_formats);
}

tcu::TestNode::IterateResult ProgramBinaryCase::iterate (void)
{
	TestLog&	log	= m_testCtx.getLog();

	if (m_formats.empty())
	{
		log << TestLog::Message << "No program binary formats are supported." << TestLog::EndMessage;

		m_testCtx.setTestResult(QP_TEST_RESULT_NOT_SUPPORTED, "Not supported");
	}
	else
	{
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

		enableLogging(true);
		test();
	}

	return STOP;
}

// Simple program binary case

class ProgramBinarySimpleCase : public ProgramBinaryCase
{
public:
	ProgramBinarySimpleCase (Context& context, const char* name, const char* desc)
		: ProgramBinaryCase(context, name, desc)
	{
	}

	virtual ~ProgramBinarySimpleCase (void)
	{
	}

	void test (void)
	{
		const std::string			vertSrc	= getSimpleShaderSource(glu::SHADERTYPE_VERTEX);
		const std::string			fragSrc	= getSimpleShaderSource(glu::SHADERTYPE_FRAGMENT);

		const glu::ProgramSources	sources	= glu::makeVtxFragSources(vertSrc, fragSrc);

		glu::ShaderProgram			program	(m_context.getRenderContext(), sources);

		if (program.isOk())
		{
			ProgramBinary binary;

			getProgramBinary(binary, program.getProgram());
			verifyProgramBinary(binary);
		}
	}
};

// Program binary uniform reset case

class ProgramBinaryUniformResetCase : public ProgramBinaryCase
{
public:
	ProgramBinaryUniformResetCase (Context& context, const char* name, const char* desc)
		: ProgramBinaryCase	(context, name, desc)
		, m_rnd				(deStringHash(name) ^ 0xf2b48c6a)
	{
	}

	virtual ~ProgramBinaryUniformResetCase (void)
	{
	}

	std::string getShaderSource (const glu::ShaderType shaderType) const
	{
		const char* vertSrc =
			"#version 300 es\n"
			"uniform bool u_boolVar;\n"
			"uniform highp int u_intVar;\n"
			"uniform highp float u_floatVar;\n\n"
			"in highp vec4 a_position;\n\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n";
		const char* fragSrc =
			"#version 300 es\n"
			"uniform bool u_boolVar;\n"
			"uniform highp int u_intVar;\n"
			"uniform highp float u_floatVar;\n\n"
			"layout(location = 0) out mediump vec4 o_fragColor;\n\n"
			"void main (void)\n"
			"{\n"
			"	mediump float refAll = float(u_boolVar) + float(u_intVar) + u_floatVar;\n"
			"	o_fragColor = vec4(refAll);\n"
			"}\n";

		DE_ASSERT(shaderType == glu::SHADERTYPE_VERTEX || shaderType == glu::SHADERTYPE_FRAGMENT);

		return (shaderType == glu::SHADERTYPE_VERTEX) ? vertSrc : fragSrc;
	}

	void setUniformsRandom (glu::ShaderProgram& program)
	{
		TestLog&		log		= m_testCtx.getLog();
		const deUint32	glProg	= program.getProgram();

		log << TestLog::Message << "Setting uniforms to random non-zero values." << TestLog::EndMessage;

		glUseProgram(glProg);

		{
			const GLint		boolLoc		= glGetUniformLocation(glProg, "u_boolVar");
			const GLint		intLoc		= glGetUniformLocation(glProg, "u_intVar");
			const GLint		floatLoc	= glGetUniformLocation(glProg, "u_floatVar");

			const deInt32	intVal		= m_rnd.getInt(1, 1000);
			const float		floatVal	= m_rnd.getFloat(1.0, 1000.0);

			glUniform1i(boolLoc,	GL_TRUE);
			glUniform1f(floatLoc,	floatVal);
			glUniform1i(intLoc,		intVal);
		}
	}

	void verifyUniformInt (glu::ShaderProgram& program, const std::string& name)
	{
		const GLint		intLoc	= glGetUniformLocation(program.getProgram(), name.c_str());
		GLint			intVar	= -1;

		glGetUniformiv(program.getProgram(), intLoc, &intVar);

		if (intVar != 0)
		{
			m_testCtx.getLog() << TestLog::Message << "Fail, expected zero value for " << name << ", received: " << intVar << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Uniform value not reset");
		}
	}

	void verifyUniformFloat (glu::ShaderProgram& program, const std::string& name)
	{
		const GLint	floatLoc	= glGetUniformLocation(program.getProgram(), name.c_str());
		GLfloat		floatVar	= -1;

		glGetUniformfv(program.getProgram(), floatLoc, &floatVar);

		if (floatVar != 0.0f)
		{
			m_testCtx.getLog() << TestLog::Message << "Fail, expected zero value for " << name << ", received: " << de::toString(floatVar) << TestLog::EndMessage;
			m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Uniform value not reset");
		}
	}

	void verifyUniformsReset (glu::ShaderProgram& program)
	{
		m_testCtx.getLog() << TestLog::Message << "Verifying uniform reset to 0/false." << TestLog::EndMessage;

		verifyUniformInt	(program,	"u_boolVar");
		verifyUniformInt	(program,	"u_intVar");
		verifyUniformFloat	(program,	"u_floatVar");
	}

	void test (void)
	{
		TestLog&					log		= m_testCtx.getLog();

		const std::string			vertSrc	= getShaderSource(glu::SHADERTYPE_VERTEX);
		const std::string			fragSrc	= getShaderSource(glu::SHADERTYPE_FRAGMENT);

		const glu::ProgramSources	sources	= glu::makeVtxFragSources(vertSrc, fragSrc);

		glu::ShaderProgram			program	(m_context.getRenderContext(), sources);

		log << program;

		TCU_CHECK_MSG(program.isOk(), "Couldn't build program");

		{
			ProgramBinary binary;

			getProgramBinary(binary, program.getProgram());
			verifyProgramBinary(binary);

			setUniformsRandom(program);

			log << TestLog::Message << "Rendering test image and reloading binary" << TestLog::EndMessage;

			drawWithProgram(m_context.getRenderContext(), program.getProgram());
			loadProgramBinary(binary, program.getProgram());

			verifyUniformsReset(program);
		}
	}
private:
	de::Random	m_rnd;
};

// Base class for program state persistence cases

class ProgramBinaryPersistenceCase : public ProgramBinaryCase
{
public:
					ProgramBinaryPersistenceCase	(Context& context, const char* name, const char* desc, glu::ShaderType shaderType);
	virtual			~ProgramBinaryPersistenceCase	(void) {}

	void			buildProgram					(glu::Program& program, ShaderAllocator& shaders);

	void			test							(void);

	virtual void	executeForProgram				(glu::Program& program, ShaderAllocator& shaders)	= 0;
	virtual void	verify							(glu::Program& program, const ProgramBinary& binary);

protected:
	de::Random				m_rnd;
	const glu::ShaderType	m_shaderType;
};

ProgramBinaryPersistenceCase::ProgramBinaryPersistenceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
	: ProgramBinaryCase	(context, name, desc)
	, m_rnd				(deStringHash(name) ^ 0x713de0ca)
	, m_shaderType		(shaderType)
{
	DE_ASSERT(m_shaderType == glu::SHADERTYPE_VERTEX || m_shaderType == glu::SHADERTYPE_FRAGMENT);
}

void ProgramBinaryPersistenceCase::buildProgram (glu::Program& program, ShaderAllocator& shaders)
{
	TestLog&		log			= m_testCtx.getLog();

	glu::Shader&	vertShader	= shaders.createShader(glu::SHADERTYPE_VERTEX);
	glu::Shader&	fragShader	= shaders.createShader(glu::SHADERTYPE_FRAGMENT);

	vertShader.compile();
	fragShader.compile();

	program.attachShader(vertShader.getShader());
	program.attachShader(fragShader.getShader());
	program.link();

	logProgram(log, m_context.getRenderContext(), program, shaders);
}

void ProgramBinaryPersistenceCase::verify (glu::Program& program, const ProgramBinary& binary)
{
	TestLog&		log				= m_testCtx.getLog();
	ProgramBinary	currentBinary;

	getProgramBinary(currentBinary, program.getProgram());

	if (!programBinariesEqual(binary, currentBinary))
	{
		log << TestLog::Message << "Fail, program binary may only change as a result of linking or loading." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Program binary changed");
	}
}

void ProgramBinaryPersistenceCase::test (void)
{
	TestLog&				log			= m_testCtx.getLog();
	glu::RenderContext&		renderCtx	= m_context.getRenderContext();

	ConstantShaderGenerator	sourceGen	(m_rnd);

	ShaderAllocator			shaders		(renderCtx, sourceGen);
	glu::Program			program		(renderCtx);

	buildProgram(program, shaders);

	if (program.getLinkStatus())
	{
		ProgramBinary binary;
		getProgramBinary(binary, program.getProgram());

		executeForProgram(program, shaders);

		verify(program, binary);

		logProgram(log, renderCtx, program, shaders);
	}
	else
	{
		log << TestLog::Message << "Fail, couldn't link program." << TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Linking failed");
	}
}

// Program state case utilities

namespace
{

template<class T>
void addProgramBinaryPersistenceCase (TestCaseGroup* group, Context& context, const std::string& name, const std::string& desc)
{
	for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
	{
		const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
		const std::string		shaderTypeName	= getShaderTypeName(shaderType);

		const std::string		caseName		= name + "_" + shaderTypeName;
		const std::string		caseDesc		= "Build program, " + desc + ", for " + shaderTypeName + " shader.";

		group->addChild(new T(context, caseName.c_str(), caseDesc.c_str(), shaderType));
	}
}

} // anonymous

// Specialized program state cases

class ProgramBinaryPersistenceDetachShaderCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceDetachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceDetachShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Detaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
	}
};

class ProgramBinaryPersistenceReattachShaderCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceReattachShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceReattachShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Reattaching " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		program.attachShader(caseShader.getShader());
	}
};

class ProgramBinaryPersistenceDeleteShaderCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceDeleteShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceDeleteShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Deleting " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		shaders.deleteShader(m_shaderType);
	}
};

class ProgramBinaryPersistenceReplaceShaderCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceReplaceShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceReplaceShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Deleting and replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		program.detachShader(caseShader.getShader());
		shaders.deleteShader(m_shaderType);
		program.attachShader(shaders.createShader(m_shaderType).getShader());
	}
};

class ProgramBinaryPersistenceRecompileShaderCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceRecompileShaderCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceRecompileShaderCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Recompiling " + std::string(getShaderTypeName(m_shaderType)) + " shader" << TestLog::EndMessage;
		caseShader.compile();
		DE_UNREF(program);
	}
};

class ProgramBinaryPersistenceReplaceSourceCase : public ProgramBinaryPersistenceCase
{
public:
	ProgramBinaryPersistenceReplaceSourceCase (Context& context, const char* name, const char* desc, glu::ShaderType shaderType)
		: ProgramBinaryPersistenceCase (context, name, desc, shaderType)
	{
	}

	virtual ~ProgramBinaryPersistenceReplaceSourceCase (void)
	{
	}

	void executeForProgram (glu::Program& program, ShaderAllocator& shaders)
	{
		TestLog&		log			= m_testCtx.getLog();
		glu::Shader&	caseShader	= shaders.get(m_shaderType);

		log << TestLog::Message << "Replacing " + std::string(getShaderTypeName(m_shaderType)) + " shader source and recompiling" << TestLog::EndMessage;
		shaders.setSource(m_shaderType);
		caseShader.compile();
		DE_UNREF(program);
	}
};

// Test group

ShaderApiTests::ShaderApiTests (Context& context)
	: TestCaseGroup(context, "shader_api", "Shader API Cases")
{
}

ShaderApiTests::~ShaderApiTests (void)
{
}

void ShaderApiTests::init (void)
{
	// create and delete shaders
	{
		TestCaseGroup* createDeleteGroup = new TestCaseGroup(m_context, "create_delete", "glCreateShader() tests");
		addChild(createDeleteGroup);

		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_vertex_shader",		"Create vertex shader object",		glu::SHADERTYPE_VERTEX));
		createDeleteGroup->addChild(new CreateShaderCase(m_context,	"create_fragment_shader",	"Create fragment shader object",	glu::SHADERTYPE_FRAGMENT));

		createDeleteGroup->addChild(new DeleteShaderCase(m_context,	"delete_vertex_fragment",	"Delete vertex shader and fragment shader"));
	}

	// compile and link
	{
		TestCaseGroup* compileLinkGroup = new TestCaseGroup(m_context, "compile_link", "Compile and link tests");
		addChild(compileLinkGroup);

		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_vertex_shader",	"Compile vertex shader",	glu::SHADERTYPE_VERTEX));
		compileLinkGroup->addChild(new CompileShaderCase(m_context,	"compile_fragment_shader",	"Compile fragment shader",	glu::SHADERTYPE_FRAGMENT));

		compileLinkGroup->addChild(new LinkVertexFragmentCase(m_context,	"link_vertex_fragment",	"Link vertex and fragment shaders"));
	}

	// shader source
	{
		TestCaseGroup* shaderSourceGroup = new TestCaseGroup(m_context, "shader_source", "glShaderSource() tests");
		addChild(shaderSourceGroup);

		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
		{
			const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;
			const std::string		shaderTypeName	= getShaderTypeName(shaderType);

			const std::string		caseName		= std::string("replace_source_") + shaderTypeName;
			const std::string		caseDesc		= std::string("Replace source code of ") + shaderTypeName + " shader.";

			shaderSourceGroup->addChild(new ShaderSourceReplaceCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType));
		}

		for (int stringLengthsInt	= 0; stringLengthsInt < 3; stringLengthsInt++)
		for (int caseNdx = 1; caseNdx <= 3; caseNdx++)
		for (int shaderTypeInt = 0; shaderTypeInt < 2; shaderTypeInt++)
		{
			const int				numSlices		= 1 << caseNdx;
			const glu::ShaderType	shaderType		= (shaderTypeInt == 1) ? glu::SHADERTYPE_FRAGMENT : glu::SHADERTYPE_VERTEX;

			const bool				explicitLengths	= (stringLengthsInt != 0);
			const bool				randomNullTerm	= (stringLengthsInt == 2);

			const deUint32			flags			= (explicitLengths	? CASE_EXPLICIT_SOURCE_LENGTHS	: 0)
													| (randomNullTerm	? CASE_RANDOM_NULL_TERMINATED	: 0);

			const std::string		caseName		= "split_source_"
													+ de::toString(numSlices)
													+ (randomNullTerm ? "_random_negative_length" : (explicitLengths ? "_specify_lengths" : "_null_terminated"))
													+ ((shaderType == glu::SHADERTYPE_FRAGMENT) ? "_fragment" : "_vertex");

			const std::string		caseDesc		= std::string((shaderType == glu::SHADERTYPE_FRAGMENT) ? "Fragment" : "Vertex")
													+ " shader source split into "
													+ de::toString(numSlices)
													+ " pieces"
													+ (explicitLengths ? ", using explicitly specified string lengths" : "")
													+ (randomNullTerm ? " with random negative length values" : "");

			shaderSourceGroup->addChild(new ShaderSourceSplitCase(m_context, caseName.c_str(), caseDesc.c_str(), shaderType, numSlices, flags));
		}
	}

	// link status and infolog
	{
		TestCaseGroup* linkStatusGroup = new TestCaseGroup(m_context, "program_state", "Program state persistence tests");
		addChild(linkStatusGroup);

		addProgramStateCase<ProgramStateDetachShaderCase>		(linkStatusGroup,	m_context,	"detach_shader",	"detach shader");
		addProgramStateCase<ProgramStateReattachShaderCase>		(linkStatusGroup,	m_context,	"reattach_shader",	"reattach shader");
		addProgramStateCase<ProgramStateDeleteShaderCase>		(linkStatusGroup,	m_context,	"delete_shader",	"delete shader");
		addProgramStateCase<ProgramStateReplaceShaderCase>		(linkStatusGroup,	m_context,	"replace_shader",	"replace shader object");
		addProgramStateCase<ProgramStateRecompileShaderCase>	(linkStatusGroup,	m_context,	"recompile_shader",	"recompile shader");
		addProgramStateCase<ProgramStateReplaceSourceCase>		(linkStatusGroup,	m_context,	"replace_source",	"replace shader source");
	}

	// program binary
	{
		TestCaseGroup* programBinaryGroup = new TestCaseGroup(m_context, "program_binary", "Program binary API tests");
		addChild(programBinaryGroup);

		{
			TestCaseGroup* simpleCaseGroup = new TestCaseGroup(m_context, "simple", "Simple API tests");
			programBinaryGroup->addChild(simpleCaseGroup);

			simpleCaseGroup->addChild(new ProgramBinarySimpleCase		(m_context,	"get_program_binary_vertex_fragment",	"Get vertex and fragment shader program binary"));
			simpleCaseGroup->addChild(new ProgramBinaryUniformResetCase	(m_context,	"uniform_reset_on_binary_load",			"Verify uniform reset on successful load of program binary"));
		}

		{
			TestCaseGroup* binaryPersistenceGroup = new TestCaseGroup(m_context, "binary_persistence", "Program binary persistence tests");
			programBinaryGroup->addChild(binaryPersistenceGroup);

			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceDetachShaderCase>		(binaryPersistenceGroup,	m_context,	"detach_shader",	"detach shader");
			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReattachShaderCase>		(binaryPersistenceGroup,	m_context,	"reattach_shader",	"reattach shader");
			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceDeleteShaderCase>		(binaryPersistenceGroup,	m_context,	"delete_shader",	"delete shader");
			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReplaceShaderCase>		(binaryPersistenceGroup,	m_context,	"replace_shader",	"replace shader object");
			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceRecompileShaderCase>	(binaryPersistenceGroup,	m_context,	"recompile_shader",	"recompile shader");
			addProgramBinaryPersistenceCase<ProgramBinaryPersistenceReplaceSourceCase>		(binaryPersistenceGroup,	m_context,	"replace_source",	"replace shader source");
		}
	}
}

} // Functional
} // gles3
} // deqp
